--[[
title: vue 组件
{
    data = function()
        return {
            count = 1,
        }
    end

    methods = {
        add = function(self)
            self.count = self.count + 1;
        end
    }
}
]] 
-- \assets\scripts\commonlib\window\component.lua

local RootWatchObject = dofile("watch.lua");
local Element = dofile("element.lua");
local Component = commonlib.inherit(Element, commonlib.gettable("commonlib.Window.Component"));

Component:Property("App");
Component:Property("Options");
Component:Property("Env");
Component:Property("WatchObject");
Component:Property("ParentComponent");

-- 组件构造函数
function Component:ctor()
    self.m_components = {};
end

-- 组件初始化
function Component:Init(options, app, xmlnode, parent, window)
    self:SetOptions(options or {});
    self:SetApp(app);

    Component._super.Init(self, {name = xmlnode.name or "Component", attr = xmlnode.attr}, parent, window);

    self:SetEnv(setmetatable({}, {__index = self:GetWindow():GetEnv()}));
    self:SetWatchObject(RootWatchObject:new():Init(self:GetOptionsData(), self:GetEnv()));
    self:InstallMethods();
    self:ParseTemplate();
    self:Compile();
    return self;
end

-- 获取选项数据, 响应式数据
function Component:GetOptionsData()
    local options = self:GetOptions();
    local data = options.data;
    if (type(data) == "function") then data = data(); end
    return type(data) == "table" and data or nil;
end

-- 安装选项方法 
function Component:InstallMethods()
    local watch_object_env = self:GetWatchObject():GetEnv();
    local options = self:GetOptions();
    local env = self:GetEnv();
    local methods = options.methods or {};
    for key, value in pairs(options.methods or {}) do 
        env[key] = function(...)
            methods[key](watch_object_env, ...);
        end 
    end
end

-- 解析模板
function Component:ParseTemplate()
    local options = self:GetOptions();
    local template = options.template or commonlib.GetFileText(options.url);
    local xmlnode = self:GetWindow():LoadXmlFromText(template);
    for i, sub_xmlnode in ipairs(xmlnode) do
        local children = self:LoadElement(sub_xmlnode, self, self:GetWindow());
        self:AddChildren(children);
    end
end

-- 加载元素
function Component:LoadElement(xmlnode, parent, window)
    if (type(xmlnode) == "string") then
        local text = string.gsub(xmlnode, "^%s*(.-)%s*$", "%1");
        local args = ""
        local fmt = string.gsub(text, "{{(.-)}}", function(code)
            args = args .. ", " .. code;
            return "%s";
        end);
        local expr = string.format([==[string.format([[%s]]%s)]==], fmt, args);
        return Element:new():Init({name = "text", attr = {["v-text"] = expr}}, parent, window);
    end
    local tagname = xmlnode.name or "div";
    if (tagname == "script") then
        local script_text = self:GetXmlNodeText(xmlnode);
        local script_file = xmlnode.attr.src;
        self:DoFile(script_file)
        self:DoString(script_text);
        return nil;
    elseif (tagname == "style") then
        return nil;
    else
        local options = self:GetComponentOptionsByTagName(tagname);
        if (options) then
            local component = Component:new():Init(options, self:GetApp(), xmlnode, parent, window);
            table.insert(self.components, component);
            return component;
        else
            local el = Element:new():Init(xmlnode, parent, window);
            for _, sub_xmlnode in ipairs(xmlnode) do
                local children = self:LoadElement(sub_xmlnode, el, window);
                el:AddChildren(children);
            end
            return el;
        end
    end
end

-- 获取元素类
function Component:GetComponentOptionsByTagName(tagname)
    tagname = string.lower(tagname);

    local parent_component = self:GetParentComponent();
    local components = self:GetOptions().components;
    local component = components and components[tagname];
    component = component or (parent_component and parent_component:GetComponentOptionsByTagName(tagname));
    component = component or self:GetApp():GetComponent(tagname);
    return component;
end

-- 挂载组件
function Component:Mount() end

-- 执行脚本
function Component:DoString(script_text) self:GetWindow():DoString(script_text); end

-- 执行脚本文件
function Component:DoFile(script_file) self:GetWindow():DoFile(script_file); end

-- 获取组件
function Component:GetComponent() return self; end

-- 编译模板
function Component:Compile()
    local __event_attributes__ = {
        onclick = true,
        ondblclick = true,
        onmousedown = true,
        onmouseup = true,
        onmousemove = true,
        onmouseover = true,
        onmouseout = true,
        onmouseenter = true,
        onmouseleave = true,
        onmousewheel = true,
        ondrag = true,
        ondragstart = true,
        ondragend = true,
        ondragover = true,
        ondragenter = true,
        ondragleave = true,
    }
    local function CompileElement(element, watch_object)
        local attrs = element:GetAttributes();
        local vfor = attrs["v-for"];

        if (vfor) then
            element:SetAttributeValue("v-for", nil);
            self:Compile_VFor(vfor, element, watch_object, CompileElement);
            return;
        end

        for attr_key, attr_value in pairs(attrs) do
            if (attr_key == "v-if") then
                self:WatchExpr(attr_value, function(newval) element:SetVisible(newval); end, watch_object);
            elseif (attr_key == "v-text") then
                self:WatchExpr(attr_value, function(val) element:SetAttributeValue("text", val or ""); end, watch_object);
            elseif (string.sub(attr_key, 1, 1) == ":" or string.sub(attr_key, 1, 7) == "v-bind:") then
                local attrName = string.sub(attr_key, string.sub(attr_key, 1, 1) == ":" and 2 or 8);
                self:WatchExpr(attr_value, function(val) element:SetAttributeValue(attrName, val); end, watch_object);
            elseif (__event_attributes__[attr_key] or string.sub(attr_key, 1, 1) == "@" or string.sub(attr_key, 1, 5) == "v-on:") then
                local attr_name = __event_attributes__[attr_key] and attr_key or (string.sub(attr_key, 1, 1) == "@" and string.sub(attr_key, 2) or string.sub(attr_key, 6));
                self:Compile_VOn(element, attr_name, attr_value, watch_object);
            end
        end
        element:ForEachChildren(function(children) CompileElement(children, watch_object) end);
    end
    CompileElement(self, self:GetWatchObject());
end

function Component:Compile_VFor(vfor, element, watch_object, compile_element)
    element:SetVisible(false);

    local inIndex = string.find(vfor, " in ", 1, true);
    if (not inIndex) then return end
    local keyexpr = string.sub(vfor, 1, inIndex -1);
    local listexpr = string.sub(vfor, inIndex + 4);

    keyexpr = commonlib.trim(keyexpr);
    listexpr = commonlib.trim(listexpr);
    if (keyexpr == "" or listexpr == "") then return end

    local val, key = string.match(keyexpr, "(%a%w-)%s*,%s*(%a%w+)");
    if (not val) then val = string.gsub(keyexpr, "[,%s]*$", "") end
    key = key or "index";

    local parent_element = element:GetParent();
    if (parent_element == nil) then return end
    local clone_elements, clone_watch_objects = {}, {};
    self:WatchExpr(listexpr, nil, function(list)
        if (type(list) ~= "table" or (#list) == 0) then return end
        for _, clone_element in ipairs(clone_elements) do
            parent_element:RemoveChildren(clone_element);
        end
        local previous_element = element;
        for index, item in ipairs(list) do
            local clone_watch_object = clone_watch_objects[index] or watch_object:NewInheritWatchObject();
            clone_watch_object:Set(key, index);
            clone_watch_object:Set(val, item);
            clone_watch_objects[index] = clone_watch_object;
            local clone_element = clone_elements[index] or element:Clone();
            clone_elements[index] = clone_element;
            clone_element:SetVisible(true);
            parent_element:InsertChildren(clone_element, previous_element, true);
            previous_element = clone_element;
            compile_element(clone_element, clone_watch_object);
        end
    end, watch_object);
end

-- v-on:event=function
function Component:Compile_VOn(element, attr_key, attr_value, watch_object)
    local isFuncExpr = string.match(attr_value, "^%s*%a[%a%d%_]*%s*$");  -- 若为一个变量名则直接取其值
    local code = string.format("return function(...)\n\t%s\nend", attr_value .. (isFuncExpr and "(...)" or ""));
    local func = commonlib.dostring(code, watch_object:GetEnv());
    element:SetAttributeValue(attr_key, func);
end

-- 监控表达式
function Component:WatchExpr(expr, callback, watch_object)
    watch_object = watch_object or self:GetWatchObject();
    watch_object:Watch(expr, callback);
end

return Component;
